/*
  Name:        PixelMap.h
  Version      0.2
  Author:      Rodrigo Luis de Souza da Silva
  Date:        04/07/2009
  Last Update: 13/09/2009
  Description: Simple class to load BMP images and modify its data.
               You can create an image manually and convert it to a
               pixel matrix and vice-versa.
*/

#include "pixelMap.h"
#include <fstream>
#include <iostream>
#ifdef WIN32
#include <windows.h>
#endif
#include <GL/glut.h>
#include <stdio.h>
#include <stdlib.h>

typedef char Byte;

using namespace std;

//////////////////////////////////////////////////////////////////
PixelMap::PixelMap(const char *fname)
	: width(0), height(0), data(0)
{
    this->grayScale = false;
    this->data = NULL;
    this->Read(fname);
}

//////////////////////////////////////////////////////////////////
PixelMap::PixelMap(bool isGrayScale)
	: width(0), height(0), data(0)
{
   this->grayScale = isGrayScale;
   this->data = NULL;
}

//////////////////////////////////////////////////////////////////
PixelMap::PixelMap()
	: width(0), height(0), data(0)
{
   this->grayScale = false;
   this->data = NULL;
}

//////////////////////////////////////////////////////////////////
PixelMap::~PixelMap()
{
	if( data )
		delete[] data;
}

//////////////////////////////////////////////////////////////////
void PixelMap::Read(const char *fname)
{
	unsigned short planes;	// number of planes in image (must be 1)
	unsigned short bpp;			// number of bits per pixel (must be 24)

	ifstream fin(fname, ios::in | ios::binary);
	if( !fin )
	{
		cerr << "File not found " << fname << '\n';
		exit(1);
	}

	fin.seekg(18, ios::cur);

	fin.read((Byte *)&width, sizeof(int));
	fin.read((Byte *)&height, sizeof(int));
	cout << "width: " << width << " height: " << height << '\n';

	fin.read((Byte *)&planes, sizeof(unsigned short));
	if( planes != 1 )
	{
		cout << "Planes from " << fname << " is not 1: " << planes << "\n";
		exit(1);
	}

	fin.read((Byte *)&bpp, sizeof(unsigned short));
	if( bpp != 24 )
	{
		cout << "Bpp from " << fname << " is not 24: " << bpp << "\n";
		exit(1);
	}

	fin.seekg(24, ios::cur);

        delete data;
        int size(width * height * 3); // size of the image in bytes (3 is to RGB component).
	data = new uByte[size];
	fin.read((Byte *)data, size);


	uByte tmp;	// temporary color storage for bgr-rgb conversion.

	for( int i(0); i <  size; i += 3 )
	{
		tmp = data[i];
		data[i] = data[i+2];
		data[i+2] = tmp;
	}
}

void PixelMap::Save(const char *fname)
{

#ifdef WIN32
   cout << endl << "Saving file \"" << fname << "\"";
	using namespace std;

   BITMAPINFOHEADER bmpInfoHeader = {0};
   // Set the size
   bmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
   // Bit count
   bmpInfoHeader.biBitCount = 24; //wBitsPerPixel;
   // Use all colors
   bmpInfoHeader.biClrImportant = 0;
   // Use as many colors according to bits per pixel
   bmpInfoHeader.biClrUsed = 0;
   // Store as un Compressed
   bmpInfoHeader.biCompression = BI_RGB;
   // Set the height in pixels
   bmpInfoHeader.biHeight = this->height;
   // Width of the Image in pixels
   bmpInfoHeader.biWidth = this->width;
   // Default number of planes
   bmpInfoHeader.biPlanes = 1;
   // Calculate the image size in bytes
   bmpInfoHeader.biSizeImage = this->width * this->height * 3;
   BITMAPFILEHEADER bfh = {0};
   // This value should be values of BM letters i.e 0�4D42
   // 0�4D = M 0�42 = B storing in reverse order to match with endian
   bfh.bfType=0x4D42;
   // or
   //bfh.bfType = �B�+(�M� << 8);
   // <<8 used to shift �M� to end
   //

   // Offset to the RGBQUAD
   bfh.bfOffBits = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER);
   // Total size of image including size of headers
   bfh.bfSize = bfh.bfOffBits + bmpInfoHeader.biSizeImage;
   // Create the file in disk to write
   void* hFile = CreateFile( fname,GENERIC_WRITE, 0,NULL,
                               CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,NULL);

   if( !hFile ) // return if error opening file
       return;

   DWORD dwWritten = 0;
   // Write the File header
   WriteFile( hFile, &bfh, sizeof(bfh), &dwWritten , NULL );
   // Write the bitmap info header
   WriteFile( hFile, &bmpInfoHeader, sizeof(bmpInfoHeader), &dwWritten, NULL );

   // Convert from RGB to BGR
   int size(width * height * 3);
   uByte *tempData = new uByte[size];
	for( int i(0); i <  size; i += 3 )
	{
		tempData[i]   = data[i+2];
      tempData[i+1] = data[i+1];
      tempData[i+2] = data[i];
	}

   // Write the RGB Data
   //WriteFile( hFile, pBitmapBits, bmpInfoHeader.biSizeImage, &dwWritten, NULL );
   WriteFile( hFile, tempData, bmpInfoHeader.biSizeImage, &dwWritten, NULL );

   // Delete temporary array
   delete[] tempData;

   // Close the file handle
   CloseHandle( hFile );
   cout << "...Done!" << endl;
#else
   cout << endl << "Warning: \"Save\" method available only for windows (for now)." << endl;
#endif
}

//////////////////////////////////////////////////////////////////
void PixelMap::CreateImage(int w, int h, uByte *d)
{
   this->width  = w;
   this->height = h;
   this->data   = d;
}

//////////////////////////////////////////////////////////////////
void PixelMap::CreateImage(int w, int h)
{
   this->width  = w;
   this->height = h;
   unsigned size(w * h * 3); // size of the image in bytes (3 is to RGB component).
   this->data = new uByte[size];
}

//////////////////////////////////////////////////////////////////
void PixelMap::ViewImage()
{
   glDrawPixels(this->GetWidth(), this->GetHeight(), GL_RGB, GL_UNSIGNED_BYTE, (uByte*) this->GetData());
}

//////////////////////////////////////////////////////////////////
uByte PixelMap::pixel_elem(int x, int y, int elem)
{
	int pos = (y*width+x) * 3 + elem;
	return data[pos];
}

//////////////////////////////////////////////////////////////////
uByte *PixelMap::pixel_pos(int x, int y)
{
	int pos = (y * width + x) * 3;
	return &data[pos];
}

//////////////////////////////////////////////////////////////////
int PixelMap::GetWidth()
{
   return this->width;
}

//////////////////////////////////////////////////////////////////
void PixelMap::SetDimensions(int width, int height)
{
   this->width = width;
   this->height = height;
   delete data;
   data = new uByte[width * height * 3]; // size of the image in bytes (3 is to RGB component).
}

//////////////////////////////////////////////////////////////////
int PixelMap::GetHeight()
{
   return this->height;
}

//////////////////////////////////////////////////////////////////
uByte* PixelMap::GetData()
{
   return this->data;
}

//////////////////////////////////////////////////////////////////
void PixelMap::SetData(uByte *d)
{
   this->data = d;
}

//////////////////////////////////////////////////////////////////
void PixelMap::ConvertToGrayScale()
{
   this->grayScale = true;
   double value;
	int k = 0;
   for(int lin = 0; lin < this->GetHeight(); lin++)
      for(int col = 0; col < this->GetWidth(); col++)
      {
         value =  0.3 * (double) data[k] + 0.59 * (double) data[k+1] + 0.11 * (double) data[k+2];
         data[k] = data[k+1] = data[k+2] = (uByte) value;
         k+=3;
      }
}

//////////////////////////////////////////////////////////////////
void PixelMap::SetRGB(int x, int y, uByte R, uByte G, uByte B)
{
	int pos = (y*width+x) * 3;
	data[pos] = R;
	data[pos+1] = G;
	data[pos+2] = B;
}

//////////////////////////////////////////////////////////////////
void PixelMap::GetRGB(int x, int y, uByte &R, uByte &G, uByte &B)
{
	int pos = (y*width+x) * 3;
	R = data[pos];
	G = data[pos+1];
	B = data[pos+2];
}

//////////////////////////////////////////////////////////////////
uByte PixelMap::GetR(int x, int y)
{
	int pos = (y*width+x) * 3;
	return data[pos];
}

//////////////////////////////////////////////////////////////////
uByte PixelMap::GetG(int x, int y)
{
	int pos = (y*width+x) * 3;
	return data[pos+1];
}

//////////////////////////////////////////////////////////////////
uByte PixelMap::GetB(int x, int y)
{
	int pos = (y*width+x) * 3;
	return data[pos+2];
}

//////////////////////////////////////////////////////////////////
void PixelMap::SetR(int x, int y, uByte R)
{
	int pos = (y*width+x) * 3;
	data[pos] = R;
}

//////////////////////////////////////////////////////////////////
void PixelMap::SetG(int x, int y, uByte G)
{
	int pos = (y*width+x) * 3;
	data[pos+1] = G;
}

//////////////////////////////////////////////////////////////////
void PixelMap::SetB(int x, int y, uByte B)
{
	int pos = (y*width+x) * 3;
	data[pos+2] = B;
}

//////////////////////////////////////////////////////////////////
void PixelMap::SetGrayValue(int x, int y, uByte value)
{
	int pos = (y*width+x) * 3;
	data[pos]   = value;
	data[pos+1] = value;
	data[pos+2] = value;
}

//////////////////////////////////////////////////////////////////
uByte PixelMap::GetGrayValue(int x, int y)
{
	int pos = (y*width+x) * 3;
	return (data[pos] + data[pos+1] + data[pos+2])/3;
}

//////////////////////////////////////////////////////////////////
void PixelMap::ConvertPixelsToData(pixel **m)
{
   int lin,col,k = 0;
   for(lin = 0; lin < this->GetHeight(); lin++)
      for(col = 0; col < this->GetWidth(); col++)
      {
         // If grayscale, set all components to value
         if(this->grayScale)
            m[lin][col].R = m[lin][col].G = m[lin][col].B = m[lin][col].value;
         data[k]   = m[lin][col].R;
         data[k+1] = m[lin][col].G;
         data[k+2] = m[lin][col].B;
         k+=3;
      }
}

//////////////////////////////////////////////////////////////////
void PixelMap::ConvertDataToPixels(pixel **m)
{
   int lin, col;
   uByte *dataAux = this->data;
   for(lin = 0; lin < this->GetHeight(); lin++)
      for(col = 0; col < this->GetWidth(); col++)
      {
         if(this->grayScale)
            m[lin][col].value = *dataAux; // All components are the same in grayscale mode
         m[lin][col].R = *dataAux++;
         m[lin][col].G = *dataAux++;
         m[lin][col].B = *dataAux++;
      }
}
